import { STUFF_TYPES } from "../constance";
import { INPUT, OUTPUT } from "../typings";

const OFFSET_SELL_IN = 1;
const EXPIRATION_SELL_IN = 0;
const LAST_TEN_DAYS_TICKET = 10;
const LAST_FIVE_DAYS_TICKET = 5;

const OFFSET_QUALITY_ONE = 1;
const OFFSET_QUALITY_TWO = 2;
const OFFSET_QUALITY_THREE = 3;

const MIN_QUALITY = 0;
const MAX_QUALITY = 50;

const isExpiration = (sellIn: number) => sellIn < EXPIRATION_SELL_IN;

const offsetBackStagePass = new Map<(p: number) => boolean, (o: OUTPUT) => void>();

offsetBackStagePass.set(
  (sellIn: number) =>
    sellIn > LAST_FIVE_DAYS_TICKET && sellIn <= LAST_TEN_DAYS_TICKET,
  (output: OUTPUT) => output.updatedQuality += OFFSET_QUALITY_TWO
);
offsetBackStagePass.set(
  (sellIn: number) =>
    sellIn <= LAST_FIVE_DAYS_TICKET && sellIn >= EXPIRATION_SELL_IN,
  (output: OUTPUT) => output.updatedQuality += OFFSET_QUALITY_THREE
);
offsetBackStagePass.set(
  (sellIn: number) => sellIn > LAST_TEN_DAYS_TICKET,
  (output: OUTPUT) => output.updatedQuality -= OFFSET_QUALITY_ONE
);
offsetBackStagePass.set(
  isExpiration,
  (output: OUTPUT) => (output.updatedQuality = MIN_QUALITY)
);

const cleanQuality = (quality: number) =>
  Math.max(MIN_QUALITY, Math.min(MAX_QUALITY, quality));

export default (input: INPUT, type: STUFF_TYPES): OUTPUT => {
  const { sellIn = MIN_QUALITY, quality = MIN_QUALITY } = input || {};

  const output = {
    updatedSellIn: sellIn,
    updatedQuality: cleanQuality(quality),
  };

  if (type !== STUFF_TYPES.SULFURAS) {
    output.updatedSellIn -= OFFSET_SELL_IN;

    if (type === STUFF_TYPES.BACKSTAGE_PASS) {
      for (let [fn, offset] of offsetBackStagePass) {
        if (fn(output.updatedSellIn)) offset(output);
      }
    } else {
      //* This is just for controlling offset which should be increased or decreased.
      const symbol = type === STUFF_TYPES.AGED_BRIE ? 1 : -1;
      output.updatedQuality += isExpiration(output.updatedSellIn)
        ? symbol * OFFSET_QUALITY_TWO
        : symbol * OFFSET_QUALITY_ONE;
    }
    output.updatedQuality = cleanQuality(output.updatedQuality);
  }

  return output;
};
